package com.lc.context;

import com.lc.aop.TransactionPostProcessor;
import com.lc.config.annos.Component;
import com.lc.config.annos.Exclude;
import com.lc.config.annos.Repository;
import com.lc.config.annos.Service;
import javafx.util.Pair;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author liuc
 * @date 2021/4/18
 * @description
 **/
@Exclude
public class BeanFactory {
    private Object PLACE_HOLDER = new Object();
    //bean id -> Object
    private Map<String, Object> beanMap = new HashMap<>();
    // object -> bean id
    private Map<Object, String> beanIdMap = new HashMap<>();
    // bean id -> beanDefinition
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    //记录class对应的bean
    private Map<Class<?>,List<Object>> beanClzMap = new HashMap<>();
    //bean处理器集合
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
    //记录需要依赖的bean
    private String[] packages;

    public BeanFactory(){
        TransactionPostProcessor transactionPostProcessor = new TransactionPostProcessor(this);
        beanMap.put("transactionPostProcessor",transactionPostProcessor);
        beanPostProcessors.add(transactionPostProcessor);
    }

    public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor){
        this.beanPostProcessors.add(beanPostProcessor);
    }

    public void setPackages(String[] packages) {
        this.packages = packages;
        parsePackage();
    }

    private void parsePackage() {
        for (String aPackage : this.packages) {
            parse(aPackage);
        }
        //处理依赖,所有bean
        propagate();
        beanPost();
    }

    private void parse(String pkg){
        PkgParser parser = new PkgParser(pkg);
        List<String> clzs = parser.results();
        for (String clz : clzs) {
            createBean(clz);
        }
    }

    private void createBean(String clz) {
        BeanDefinition beanDefinition = new BeanDefinition(clz);
        if(beanDefinition.isBean()){
            String beanId = beanDefinition.getBeanId();
            Object o = beanDefinition.getBean();
            putBeanDefinitionMap(beanId,beanDefinition);
            putBeanMap(beanId,o);
            putBeanClzMap(beanId,o,o);
            beanDefinition.setBean(o);
        }
    }

    private void propagate() {
        Set<String> ids = beanMap.keySet();
        for (String id : ids) {
            Object v = beanMap.get(id);
            BeanDefinition bd = beanDefinitionMap.get(id);
            if (bd == null) continue;
            List<Field> fields = bd.getNeedPropagateFields();
            for (Field field : fields) {
                Object o = getBean(field.getType());
                //暴力访问
                field.setAccessible(true);
                try {
                    field.set(v,o);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void beanPost(){
        beanDefinitionMap.forEach((k,v) -> {
            Object o = beanMap.get(k);
            Object proxy = o;
            //直接调用增强，先创建代理类，后续再处理依赖问题
            if(v.isNeedProxy()){
                for (BeanPostProcessor beanPostProcessor : this.beanPostProcessors) {
                    Object result = beanPostProcessor.afterProcessor(o,k);
                    if(result != null){
                        proxy = result;
                    }
                }
            }
            v.setBean(proxy);
            putBeanMap(k,proxy);
            putBeanClzMap(k,proxy,o);
        });
    }

    public Object getBean(String id){
        return beanMap.get(id);
    }

    public Object getBean(Class<?> type) {
        List<Object> list = beanClzMap.get(type);
        if(list == null || list.size() == 0){
            throw new NullPointerException("can not find ["+ type.getName() +"] bean");
        }
        if(list.size() > 1){
            throw new RuntimeException("find more than one ["+ type.getName() +"] beans!");
        }
        return list.get(0);
    }

    public BeanDefinition getBeanDefinition(String beanId){
        return beanDefinitionMap.get(beanId);

    }

    public void putBeanMap(String id, Object o){
        beanMap.put(id,o);
        beanIdMap.put(o,id);
    }

    public void putBeanDefinitionMap(String id, BeanDefinition beanDefinition){
        beanDefinitionMap.put(id,beanDefinition);
    }

    public void putBeanClzMap(String beanId ,Object o, Object origin){
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanId);
        for (Class<?> c : beanDefinition.getClzs()) {
            beanClzMap.putIfAbsent(c,new ArrayList<>());
            beanClzMap.get(c).remove(origin);
            beanClzMap.get(c).add(o);
        }
    }

}
